//
// Created by root on 4/29/17.
//

#include "../runner_scheme.h"

// (begin (+ 1 2) (* 2 3)) => 6
static void test_op_begin() {
    Cell* add = make_symbol(global_scheme, "+");
    Cell* add_1 = make_integer(global_scheme, 1);
    Cell* add_2 = make_integer(global_scheme, 2);
    Cell* mul = make_symbol(global_scheme, "*");
    Cell* mul_1 = make_integer(global_scheme, 2);
    Cell* mul_2 = make_integer(global_scheme, 3);
    global_scheme->op = cell_op_begin;
    global_scheme->code = LIST_2(LIST_3(add, add_1, add_2), LIST_3(mul, mul_1, mul_2));

    ASSERT_OP_CURRENT(begin);
    op_begin(global_scheme);
    EVAL_CODE_AND_RETURN(LIST_3(add, add_1, add_2), make_integer(global_scheme, 3));

    ASSERT_OP_CURRENT(begin);
    op_begin(global_scheme);
    EVAL_CODE_AND_RETURN(LIST_3(mul, mul_1, mul_2), make_integer(global_scheme, 6));

    ASSERT_EMPTY_OP_IN_STACK;
}

// (if #t 3) => 3
static void test_op_if_case1() {
    Cell* condition = &g_true;
    Cell* true_body = make_integer(global_scheme, 3);
    global_scheme->op = cell_op_if0;
    global_scheme->code = LIST_2(condition, true_body);

    ASSERT_OP_CURRENT(if0);
    op_if0(global_scheme);
    EVAL_CODE_AND_RETURN(condition, &g_true);

    ASSERT_OP_CURRENT(if1);
    op_if1(global_scheme);
    EVAL_CODE_AND_RETURN(true_body, true_body);

    ASSERT_EMPTY_OP_IN_STACK;
}

// (if #f 3) => ()
static void test_op_if_case2() {
    Cell* condition = &g_false;
    Cell* true_body = make_integer(global_scheme, 3);
    global_scheme->op = cell_op_if0;
    global_scheme->code = LIST_2(condition, true_body);

    ASSERT_OP_CURRENT(if0);
    op_if0(global_scheme);
    EVAL_CODE_AND_RETURN(condition, &g_false);

    ASSERT_OP_CURRENT(if1);
    op_if1(global_scheme);
    EVAL_CODE_AND_RETURN(&g_nil, &g_nil);

    ASSERT_EMPTY_OP_IN_STACK;
}

// (if #t 3 4) => 3
static void test_op_if_case3() {
    Cell* condition = &g_true;
    Cell* true_body = make_integer(global_scheme, 3);
    Cell* false_body = make_integer(global_scheme, 4);
    global_scheme->op = cell_op_if0;
    global_scheme->code = LIST_3(condition, true_body, false_body);

    ASSERT_OP_CURRENT(if0);
    op_if0(global_scheme);
    EVAL_CODE_AND_RETURN(condition, &g_true);

    ASSERT_OP_CURRENT(if1);
    op_if1(global_scheme);
    EVAL_CODE_AND_RETURN(true_body, true_body);

    ASSERT_EMPTY_OP_IN_STACK;
}

// (if #f 3 4) => 4
static void test_op_if_case4() {
    Cell* condition = &g_false;
    Cell* true_body = make_integer(global_scheme, 3);
    Cell* false_body = make_integer(global_scheme, 4);
    global_scheme->op = cell_op_if0;
    global_scheme->code = LIST_3(condition, true_body, false_body);

    ASSERT_OP_CURRENT(if0);
    op_if0(global_scheme);
    EVAL_CODE_AND_RETURN(condition, &g_false);

    ASSERT_OP_CURRENT(if1);
    op_if1(global_scheme);
    EVAL_CODE_AND_RETURN(false_body, false_body);

    ASSERT_EMPTY_OP_IN_STACK;
}

//(cond ((> 1 0) 10)) => 10
static void test_op_cond_case1() {
    Cell* con1  = LIST_3(make_symbol(global_scheme, ">"), make_integer(global_scheme, 1), make_integer(global_scheme, 0));
    Cell* res1  = make_integer(global_scheme, 10);
    Cell* wrap1 = LIST_2(con1, res1);
    global_scheme->op = cell_op_cond0;
    global_scheme->code = LIST_1(wrap1);

    ASSERT_OP_CURRENT(cond0);
    op_cond0(global_scheme);
    EVAL_CODE_AND_RETURN(con1, &g_true);

    ASSERT_OP_CURRENT(cond1);
    op_cond1(global_scheme);

    ASSERT_OP_CURRENT(begin);
    op_begin(global_scheme);
    EVAL_CODE_AND_RETURN(res1, res1);

    ASSERT_EMPTY_OP_IN_STACK;
}

//(cond ((> 0 1) 10) (#t 0)) => 0
static void test_op_cond_case2() {
    Cell* con1  = LIST_3(make_symbol(global_scheme, ">"), make_integer(global_scheme, 0), make_integer(global_scheme, 1));
    Cell* res1  = make_integer(global_scheme, 10);
    Cell* wrap1 = LIST_2(con1, res1);
    Cell* con2  = &g_true;
    Cell* res2  = make_integer(global_scheme, 0);
    Cell* wrap2 = LIST_2(con2, res2);
    global_scheme->op = cell_op_cond0;
    global_scheme->code = LIST_2(wrap1, wrap2);

    ASSERT_OP_CURRENT(cond0);
    op_cond0(global_scheme);
    EVAL_CODE_AND_RETURN(con1, &g_false);

    ASSERT_OP_CURRENT(cond1);
    op_cond1(global_scheme);
    EVAL_CODE_AND_RETURN(con2, &g_true);

    ASSERT_OP_CURRENT(cond1);
    op_cond1(global_scheme);

    ASSERT_OP_CURRENT(begin);
    op_begin(global_scheme);
    EVAL_CODE_AND_RETURN(res2, res2);

    ASSERT_EMPTY_OP_IN_STACK;
}

#define INT(_x)       make_integer(global_scheme, _x)

// (case (* 2 3) ((2 3 5 7) 'prime) ((4 6 8 9) 'composite)) => composite
static void test_op_case_case1() {
    Cell* cond_expr = LIST_3(make_symbol(global_scheme, "*"), INT(2), INT(3));
    Cell* test1 = LIST_4(INT(2), INT(3), INT(5), INT(7));
    Cell* body1 = make_symbol(global_scheme, "prime");
    Cell* wrap1 = LIST_2(test1, body1);
    Cell* test2 = LIST_4(INT(4), INT(6), INT(8), INT(9));
    Cell* body2 = make_symbol(global_scheme, "composite");
    Cell* wrap2 = LIST_2(test2, body2);
    global_scheme->op = cell_op_case0;
    global_scheme->code = LIST_3(cond_expr, wrap1, wrap2);

    ASSERT_OP_CURRENT(case0);
    op_case0(global_scheme);
    EVAL_CODE_AND_RETURN(cond_expr, INT(6));

    ASSERT_OP_CURRENT(case1);
    op_case1(global_scheme);
    ASSERT_CODE_CURRENT(LIST_1(body2));

    ASSERT_OP_CURRENT(begin);
    op_begin(global_scheme);
    EVAL_CODE_AND_RETURN(body2, body2);

    ASSERT_EMPTY_OP_IN_STACK;
}

int main(int argc, char **argv) {
    const struct CMUnitTest tests[] = {
            cmocka_unit_test(test_op_begin),
            cmocka_unit_test(test_op_if_case1),
            cmocka_unit_test(test_op_if_case2),
            cmocka_unit_test(test_op_if_case3),
            cmocka_unit_test(test_op_if_case4),
            cmocka_unit_test(test_op_cond_case1),
            cmocka_unit_test(test_op_cond_case2),
            cmocka_unit_test(test_op_case_case1),
    };
    return cmocka_run_group_tests(tests, setup_scheme, teardown_scheme);
}
